Skip to content

feat: add merge() method + basedpyright type checking#41

Merged
nathanjmcdougall merged 15 commits into
mainfrom
feature/deep-merge
May 25, 2026
Merged

feat: add merge() method + basedpyright type checking#41
nathanjmcdougall merged 15 commits into
mainfrom
feature/deep-merge

Conversation

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator

@nathanjmcdougall nathanjmcdougall commented May 22, 2026

Summary

Adds a merge() method to Document and Editor that recursively merges a value into an existing mapping without removing extra keys, and adds basedpyright static type checking to the project.

Deep Merge

Complements sync() which enforces exact structure (removing keys not in the target). merge() is additive — it updates matching keys, adds new keys, and never deletes.

Semantics

Current type Target type Behavior
mapping mapping Recurse: update matching keys, add new keys, never remove
list list Replace entirely
scalar mapping Replace (promote)
any any (equal) No-op
missing any Create path via upsert

Example

doc = loads("""
settings:
  debug: true
  log_level: info
  custom_setting: 42
""")

doc = doc.merge("settings", value={"debug": False, "timeout": 30})
# Result: debug updated, timeout added, log_level and custom_setting preserved

Basedpyright Type Checking

  • Added basedpyright as dev dependency with strict-ish config in [tool.basedpyright]
  • Added typing_extensions as runtime dependency (for @override on Python 3.10/3.11)
  • Integrated as pre-commit hook (runs in CI via prek)
  • All violations resolved through proper fixes (not suppressions):
    • Switched to relative imports to eliminate import cycles
    • Removed redundant Component.key()/Component.index() static factory methods
    • Retyped _create_at to accept tuple[str, ...] with _ensure_str_keys() boundary
    • Made normalize_keys and compute_patches public (with tests)
    • Added @override decorators and explicit type annotations

Breaking Changes

  • Component.key(name) / Component.index(idx) removed — Use Component.Key(name) / Component.Index(idx) constructors instead. The static factory methods were redundant wrappers around the enum variant constructors and caused type-checking conflicts. This is acceptable under pre-1.0 semver (current version: 0.5.0).

Changes

Merge

  • src/yamltrip/sync.pycompute_patches gains remove_extra keyword (default True) + DiffMode enum
  • src/yamltrip/document.pyDocument.merge() method + fix for _create_at nested dict flattening bug
  • src/yamltrip/editor.pyEditor.merge() delegate
  • tests/test_merge.py — 12 tests covering core merge, edge cases, and Editor integration

Type Checking

  • pyproject.toml — basedpyright config + typing_extensions dependency
  • .pre-commit-config.yaml — basedpyright hook
  • src/yamltrip/_core.pyi@override decorators, removed static factories
  • src/yamltrip/document.py — relative imports, type annotations, @override
  • src/yamltrip/editor.py — relative imports, type annotations, @override
  • src/yamltrip/sync.py — relative imports
  • src/types.rs — removed static factory methods, updated __repr__
  • tests/test_normalize_keys.py — 8 tests for normalize_keys
  • tests/test_compute_patches.py — 10 tests for compute_patches
  • .importlinter — removed stale ignore rules (no longer needed with relative imports)

Op.merge_into flattens nested dicts to the wrong indentation level when
creating intermediate keys (e.g. upsert('parent', 'child', value={'x': 1})).
Fix by using add(key, None) + replace(value) which correctly serializes
the full nested structure.

Adds a regression test that fails without the fix.
Add test classes for list replacement, type promotion, path creation, and no-op merge scenarios. Fix _create_at to correctly handle nested dict values by using placeholder+replace instead of direct Op.add which flattens nested structures.
- Short-circuit list diffing in merge (replace entirely per spec)
- Add root-exists comment to merge() for consistency with sync()
- Add flow sequence tests to test_merge.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an additive deep-merge capability to yamltrip by introducing merge() on Document (immutable) and Editor (mutable), implemented via existing patch-diff machinery with a new remove_extra toggle to prevent key deletions during mapping diffs.

Changes:

  • Introduces Document.merge() and Editor.merge() to perform recursive additive merges (never deletes mapping keys; lists replace entirely).
  • Extends src/yamltrip/sync.py diff engine with remove_extra to support “sync” vs “merge” semantics using the same patch computation.
  • Adds merge-focused tests plus a design/semantics spec document.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_merge.py Adds unit tests covering mapping merge, list replacement, type promotion, missing-path creation, no-op behavior, and Editor integration.
src/yamltrip/sync.py Adds remove_extra flag to mapping/list diff logic to enable additive merge behavior without deleting extra keys.
src/yamltrip/editor.py Adds Editor.merge() delegating to Document.merge().
src/yamltrip/document.py Adds Document.merge() implementation using _compute_patches(..., remove_extra=False) plus flow-sequence handling similar to sync().
doc/specs/2026-05-22-deep-merge-design.md Documents merge semantics, API, and testing plan (needs minor corrections per review comments).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread doc/specs/2026-05-22-deep-merge-design.md
Comment thread doc/specs/2026-05-22-deep-merge-design.md Outdated
Comment thread doc/specs/2026-05-22-deep-merge-design.md
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread src/yamltrip/document.py Outdated
Comment thread src/yamltrip/document.py Outdated
Comment thread doc/specs/2026-05-22-deep-merge-design.md
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Comment thread src/yamltrip/document.py
Comment thread src/yamltrip/document.py Outdated
Comment thread src/yamltrip/document.py Outdated
Comment thread src/yamltrip/sync.py
…ode, rewrite docstring, introduce DiffMode enum
- Add basedpyright as dev dependency
- Add typing_extensions as runtime dependency (for @OverRide on py3.10/3.11)
- Configure [tool.basedpyright] with appropriate suppressions
- Add basedpyright hook to .pre-commit-config.yaml (runs in CI via prek)
- Switch to relative imports to break import cycles
- Remove redundant Component.key()/Component.index() static factories
- Retype _create_at child_keys as tuple[str, ...] with _ensure_str_keys
- Make normalize_keys and compute_patches public (with tests)
- Add @OverRide decorators and type annotations for class attributes
- Enable reportUnusedCallResult, reportUnknownMemberType,
  reportUnnecessaryIsInstance, reportImplicitStringConcatenation
@nathanjmcdougall nathanjmcdougall changed the title feat: add merge() method for additive deep merge feat: add merge() method + basedpyright type checking May 25, 2026
@nathanjmcdougall nathanjmcdougall requested a review from Copilot May 25, 2026 04:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Comment thread src/yamltrip/sync.py
Comment thread src/types.rs
Comment thread src/yamltrip/_core.pyi
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.

Comment thread .pre-commit-config.yaml
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@nathanjmcdougall nathanjmcdougall merged commit 379171a into main May 25, 2026
18 checks passed
@nathanjmcdougall nathanjmcdougall deleted the feature/deep-merge branch May 25, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants